/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

import { csmVector, iterator } from '@framework/type/csmvector';

import { gl } from './lappdelegate';
import LAppDefine from './lappdefine';
import { cacheFetch } from './cache';

/**
 * テクスチャ管理クラス
 * 画像読み込み、管理を行うクラス。
 */
export class LAppTextureManager {
    /**
     * コンストラクタ
     */
    constructor() {
        this._textures = new csmVector<TextureInfo>();
    }

    /**
     * 解放する。
     */
    public release(): void {
        for (
            let ite: iterator<TextureInfo> = this._textures.begin();
            ite.notEqual(this._textures.end());
            ite.preIncrement()
        ) {
            gl.deleteTexture(ite.ptr().id);
        }
        this._textures = null;
    }

    /**
     * 从远程载入 材质 文件，可以是 png，也可以是 webp
     *
     * @param fileName 読み込む画像ファイルパス名
     * @param usePremultiply Premult処理を有効にするか
     * @return 画像情報、読み込み失敗時はnullを返す
     */
    public async createTextureFromFile(
        fileName: string,
        usePremultiply: boolean,
        callback: (textureInfo: TextureInfo) => void
    ): Promise<void> {
        // search loaded texture already
        for (
            let ite: iterator<TextureInfo> = this._textures.begin();
            ite.notEqual(this._textures.end());
            ite.preIncrement()
        ) {
            if (
                ite.ptr().fileName == fileName &&
                ite.ptr().usePremultply == usePremultiply
            ) {
                // 2回目以降はキャッシュが使用される(待ち時間なし)
                // WebKitでは同じImageのonloadを再度呼ぶには再インスタンスが必要
                // 詳細：https://stackoverflow.com/a/5024181
                ite.ptr().img = new Image();
                ite.ptr().img.onload = (): void => callback(ite.ptr());
                ite.ptr().img.src = fileName;
                return;
            }
        }

        // データのオンロードをトリガーにする
        const img = new Image();
        
        img.onload = (): void => {
            // テクスチャオブジェクトの作成
            const tex: WebGLTexture = gl.createTexture();

            // テクスチャを選択
            gl.bindTexture(gl.TEXTURE_2D, tex);

            // テクスチャにピクセルを書き込む
            gl.texParameteri(
                gl.TEXTURE_2D,
                gl.TEXTURE_MIN_FILTER,
                gl.LINEAR_MIPMAP_LINEAR
            );
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

            // Premult処理を行わせる
            if (usePremultiply) {
                gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
            }

            // テクスチャにピクセルを書き込む
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);

            // ミップマップを生成
            gl.generateMipmap(gl.TEXTURE_2D);

            // テクスチャをバインド
            gl.bindTexture(gl.TEXTURE_2D, null);

            const textureInfo: TextureInfo = new TextureInfo();
            if (textureInfo != null) {
                textureInfo.fileName = fileName;
                textureInfo.width = img.width;
                textureInfo.height = img.height;
                textureInfo.id = tex;
                textureInfo.img = img;
                textureInfo.usePremultply = usePremultiply;
                this._textures.pushBack(textureInfo);
            }

            callback(textureInfo);
        };

        const imageFormat = fileName.split('.').at(-1).toLowerCase();
        const response = await cacheFetch(fileName);
        const arraybuffer = await response.arrayBuffer();
        const blob = new Blob([arraybuffer], { type: 'image/' + imageFormat });
        const url = URL.createObjectURL(blob);
        img.src = url;
    }

    /**
     * 画像の解放
     *
     * 配列に存在する画像全てを解放する。
     */
    public releaseTextures(): void {
        for (let i = 0; i < this._textures.getSize(); i++) {
            this._textures.set(i, null);
        }

        this._textures.clear();
    }

    /**
     * 画像の解放
     *
     * 指定したテクスチャの画像を解放する。
     * @param texture 解放するテクスチャ
     */
    public releaseTextureByTexture(texture: WebGLTexture): void {
        for (let i = 0; i < this._textures.getSize(); i++) {
            if (this._textures.at(i).id != texture) {
                continue;
            }

            this._textures.set(i, null);
            this._textures.remove(i);
            break;
        }
    }

    /**
     * 画像の解放
     *
     * 指定した名前の画像を解放する。
     * @param fileName 解放する画像ファイルパス名
     */
    public releaseTextureByFilePath(fileName: string): void {
        for (let i = 0; i < this._textures.getSize(); i++) {
            if (this._textures.at(i).fileName == fileName) {
                this._textures.set(i, null);
                this._textures.remove(i);
                break;
            }
        }
    }

    _textures: csmVector<TextureInfo>;
}

/**
 * 画像情報構造体
 */
export class TextureInfo {
    img: HTMLImageElement; // 画像
    id: WebGLTexture = null; // テクスチャ
    width = 0; // 横幅
    height = 0; // 高さ
    usePremultply: boolean; // Premult処理を有効にするか
    fileName: string; // ファイル名
}
